import { Col } from "../core/data_ext";
import { Waiter } from "../core/utils";

/**
 * 范例：
 * 
        let ani = new Animation(1000, src, {
            y: -Utils.app.root.ele.clientHeight + box.top - 150 - this.startY * 20,
            scale: 1,
            opacity: 0
        });
        this.startY += 1;
        ani.play(16, "linear", () => {
            float.y = src.y;
            float.float.style.opacity = `${src.opacity}`;
            float.float.style.scale = `${src.scale}`;
        }).await().then(() => {
            this.startY -= 1;
            float.dispose();
        })

 * 范例：
        let ani3 = new CSSAnimation(250, fromCharAvatar.ele, {
          left: srcLeft,
        }, {
          left: "px",
        });
        await ani3.play().await();
 * 
 */

export class Animation {
    intervalId: number = 0;
    waiter: Waiter<void> = null;
    target: any = null;
    src: any = null;
    dst: any = null;
    during = 0;
    cur = 0;
    protected _keys: string[] = [];

    constructor(during: number, target: any, dst: any) {
        this.during = during;
        this.target = target;
        this.dst = dst;
        this.src = {};
        this._keys = Object.keys(this.dst);
        for (let i = 0; i < this._keys.length; i++) {
            let key = this._keys[i];
            this.src[key] = this.target[key];
        }
    }
    play(inc = 16, easing = "linear", updateCb = null) {
        if (this.intervalId !== 0) {
            return this.waiter;
        }
        this.intervalId = setInterval(() => {
            this.cur += inc;
            let pc = Math.max(0, Math.min(1.0, this.cur / this.during));

            for (let i = 0; i < this._keys.length; i++) {
                let key = this._keys[i];
                this.target[key] = Lerp(this.src[key], this.dst[key], pc, easing);
            }

            updateCb && updateCb();

            if (pc === 1) {
                this.stop();
            }
        }, inc);
        this.waiter = new Waiter();
        return this.waiter;
    }
    stop() {
        if (this.intervalId === 0) {
            return;
        }
        if (this.waiter) {
            this.waiter.finish();
            this.waiter = null;
        }
        clearInterval(this.intervalId);
        this.intervalId = 0;
    }
}

export class CSSAnimation extends Animation {
    ele: HTMLElement = null;
    unitName: Col<string> = {};
    constructor(during: number, ele: HTMLElement, dst: any, unitName: Col<string>) {
        super(during, {}, dst);
        this.ele = ele;
        this.unitName = unitName;

        this.during = during;
        this.dst = dst;
        this.src = {};
        this._keys = Object.keys(this.dst);
        for (let i = 0; i < this._keys.length; i++) {
            let key = this._keys[i];
            let val = Number(this.ele.style[key].replace(unitName[key], ""));
            if (!isNaN(val)) {
                this.src[key] = val;
            }
            else {
                //todo:
                this.src[key] = 0;
            }
        }

    }
    play(inc = 16, easing = "linear", updateCb = null) {
        if (this.intervalId !== 0) {
            return this.waiter;
        }
        this.intervalId = setInterval(() => {
            this.cur += inc;
            let pc = Math.max(0, Math.min(1.0, this.cur / this.during));

            for (let i = 0; i < this._keys.length; i++) {
                let key = this._keys[i];
                this.target[key] = Lerp(this.src[key], this.dst[key], pc, easing);
            }

            this.updateCSS();

            updateCb && updateCb();

            if (pc === 1) {
                this.stop();
            }
        }, inc);
        this.waiter = new Waiter();
        return this.waiter;
    }
    updateCSS() {
        for (let key in this.target) {
            this.ele.style[key] = `${this.target[key]}${this.unitName[key]}`;
        }
    }
};

function Lerp(n1: number, n2: number, pc: number, ease: string = "linear") {
    let t = n1 + (n2 - n1) * pc;
    return Easing[ease](t);
}

const Easing = {
    linear: (t) => {
        return t;
    },
    // 二次方缓动（Ease In Quad）
    easeInQuad: (t) => {
        return t * t;
    },
    // 二次方缓出（Ease Out Quad）
    easeOutQuad: (t) => {
        return t * (2 - t);
    },
    // 二次方缓动（Ease In Out Quad）
    easeInOutQuad: (t) => {
        return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
    },
    // 三次方缓入（Ease In Cubic）
    easeInCubic: (t) => {
        return t * t * t;
    },
    // 三次方缓出（Ease Out Cubic）
    easeOutCubic: (t) => {
        return (--t) * t * t + 1;
    },
    // 三次方缓动（Ease In Out Cubic）
    easeInOutCubic: (t) => {
        return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
    },
    // 正弦缓入（Ease In Sine）
    easeInSine: (t) => {
        return 1 - Math.cos(t * Math.PI / 2);
    },
    // 正弦缓出（Ease Out Sine）
    easeOutSine: (t) => {
        return Math.sin(t * Math.PI / 2);
    },
    // 正弦缓动（Ease In Out Sine）
    easeInOutSine: (t) => {
        return -0.5 * (Math.cos(Math.PI * t) - 1);
    }
};